package ants;

import java.util.List;
import java.util.Scanner;
import java.util.regex.Pattern;

import cell.Ant;
import cell.Cell;
import cell.Dead;
import cell.Food;
import cell.Hill;
import cell.Visible;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.common.collect.ImmutableSetMultimap;


/**
 * Used to build Properties instances
 * 
 */
public class PropertiesBuilder {
  
  public static enum UpdateToken {
    A, D, F, H, W;
    
    static final Pattern PATTERN = GameState.compilePattern(UpdateToken.values( ));
  }
  
  
  private final ImmutableSetMultimap.Builder<CellProperty, Cell> builder = ImmutableSetMultimap.builder( );
  
  
  public PropertiesBuilder( ) {}
  
  
  /**
   * Parses the update information from system input and puts that information
   * into this PropertiesBuilder. Does not build a set for LAND since we don't
   * have all the WATER information. That's done when we use the Properties
   * object made from of this PropertiesBuilder to update the previous state
   * using updatePropertySetsMap.
   * 
   * @param input
   *          update information
   * 
   * @return
   */
  public PropertiesBuilder(List<String> input) {
    
    final ImmutableSet.Builder<Cell> myAntsBuilder = new ImmutableSet.Builder<Cell>( );
    
    for (final String line: input) {
      
      final Scanner scanner = new Scanner(line);
      if (!scanner.hasNext( )) continue;
      
      final String token = scanner.next( ).toUpperCase( );
      if (!UpdateToken.PATTERN.matcher(token).matches( )) continue;
      
      final UpdateToken updateToken = UpdateToken.valueOf(token);
      
      final int row = scanner.nextInt( );
      final int col = scanner.nextInt( );
      
      switch (updateToken) {
        case A:
          if (scanner.hasNextInt( )) addAnt(row, col, scanner.nextInt( ), myAntsBuilder);
          break;
        case D:
          if (scanner.hasNextInt( )) addDeadAnt(row, col, scanner.nextInt( ));
          break;
        case F:
          addFood(row, col);
          break;
        case H:
          if (scanner.hasNextInt( )) addHill(row, col, scanner.nextInt( ));
          break;
        case W:
          addWater(row, col);
          break;
      }
    }
    addVisibleCells(myAntsBuilder.build( ));
  }
  
  
  /**
   * Adds new ant.
   * 
   * @param row
   *          row index
   * @param col
   *          column index
   * @param owner
   *          player id
   * @param myAntsBuilder
   * @param myAntBuilder
   *          collects MY_ANTs
   * 
   */
  void addAnt(int row, int col, int owner, Builder<Cell> myAntsBuilder) {
    final Ant ant = new Ant(row, col);
    addCellWithProperty(ant, owner > 0 ? CellProperty.ENEMY_ANT : CellProperty.MY_ANT);
    if (owner == 0) myAntsBuilder.add(ant);
  }
  
  
  /**
   * Updates game state information about a property.
   * 
   * @param cell
   *          location on the game map to be updated
   * @param cellProperty
   *          new CellProperty to be added
   * 
   */
  public void addCellWithProperty(Cell cell, CellProperty cellProperty) {
    builder.put(cellProperty, cell);
  }
  
  
  /**
   * Adds dead ant.
   * 
   * @param row
   *          row index
   * @param col
   *          column index
   * @param _owner
   * @param owner
   *          player id
   * 
   */
  void addDeadAnt(int row, int col, int _owner) {
    addCellWithProperty(new Dead(row, col), CellProperty.DEAD_ANT);
  }
  
  
  /**
   * Adds new food.
   * 
   * @param row
   *          row index
   * @param col
   *          column index
   */
  void addFood(int row, int col) {
    addCellWithProperty(new Food(row, col), CellProperty.FOOD);
  }
  
  
  /**
   * Adds new hill.
   * 
   * @param row
   *          row index
   * @param col
   *          column index
   * @param owner
   *          player id
   */
  public void addHill(int row, int col, int owner) {
    addCellWithProperty(new Hill(row, col), owner == 0 ? CellProperty.MY_HILL : CellProperty.ENEMY_HILL);
  }
  
  
  /**
   * Add VISIBLE and LAND cells to the Builder.
   * 
   * @param myAnts
   *          Ants from this input
   */
  public void addVisibleCells(ImmutableSet<Cell> myAnts) {
    // timesSeenX.clear( );
    // antDensityX.clear( );
    for (final Cell myAnt: myAnts) {
      for (final Cell locOffset: GameState.visionOffsets) {
        final Cell cell = applyOffset(myAnt, locOffset);
        addCellWithProperty(cell, CellProperty.VISIBLE);
      }
    }
  }
  
  
  /**
   * Add new water.
   * 
   * @param row
   *          row index
   * @param col
   *          column index
   */
  public void addWater(int row, int col) {
    final Cell cell = new Cell(row, col);
    addCellWithProperty(cell, CellProperty.WATER);
  }
  
  
  public ImmutableSetMultimap<CellProperty, ? extends Cell> build( ) {
    return builder.build( );
  }
  
  
  @Override
  public String toString( ) {
    return new Properties(build( )).toString( );
  }
  
  
  /**
   * Returns location with the specified offset from the specified location.
   * 
   * @param mapLoc
   *          location on the game map
   * @param offset
   *          offset to look up
   * 
   * @return location with <code>offset</code> from <cod>mapLoc</code>
   */
  private static Cell applyOffset(Cell mapLoc, Cell offset) {
    int row = (mapLoc.row + offset.row) % GameState.rows;
    if (row < 0) row += GameState.rows;
    int col = (mapLoc.col + offset.col) % GameState.cols;
    if (col < 0) col += GameState.cols;
    return new Visible(row, col);
  }
  
  
}
